--[[ 四国战机1脚本
  作用是显在自机位置显示判定方块和十字辅助线,还有显示诸如火力之类的数据
  可作为彩京等stg游戏的模版
  
  这货与<武装飞鸟1>相似又不同,虽然有四个xy坐标表示方框,但不是完整的方框只是一半?
  我觉得机体宽度可能是偶数所以中心点像素不存在,用对称法计算了另一侧效果不错.
  但一个难顶的事实是YDOWN_ADDR_HIGH根本找不到!但好在所有机体YDOWN和YUP数值的差都是10,
  可以通过差值直接计算.
  之后再估计一下向上大概多少个像素会被命中就行了,貌似是5像素?
  
  把2P的地址也找了,可以通过GAME.setPlayer()函数选择1p还是2p,四国战机1代有次必要
  
  发现1p和2p的按键port接口不同,投币和开始又是另一个接口,颇为麻烦!
  又发现1p和2p的Button 1的field根本无效,set_value为1无效也不会阻碍按键实际按下,怀疑这两个接口
  根本接错了,真正的接口没有暴露...这没法搞,还好选人可以用Button 2
  更正:元凶发现!是autofire插件的锅!会把其他可能按下键的方法封锁!是什么代码暂时不知道!
  目前发现用inputmacro做连发没事,而武装飞鸟1则是用autofire也没事,真难顶!
  
  更新使用了自己写的MyMame库,目前该库有绑定时防错防暂停时cpu飙升bug的功能,
  还有设定脚本热键的功能,做了个player切换
  
  By setycyas @2025-02-09
]]

local myMame = require('MyMame')

-- 这里设定基本参数
local GAME = {
  -- 脚本名,显示识别运行等使用
  NAME = "aerofgt_autoboot",
  -- 分辨率
  X_RESOLUTION = 224.0,
  Y_RESOLUTION = 320.0,
  -- 横坐标只需要低位,纵坐标需要高低位,无法用读取双字节的方法直接读数据
  XLEFT_ADDR_LOW = 0xff0711,
  XRIGHT_ADDR_LOW = 0xff06cf,
  YDOWN_ADDR_LOW = 0xff0713,
  --YDOWN_ADDR_HIGH = 0xff0713-1,
  YUP_ADDR_LOW = 0xff06d1,
  YUP_ADDR_HIGH = 0xff06d1-1,
  -- 像素平移
  --X_OFFSET = 5,
  --Y_OFFSET = 5,
  -- 火力地址
  POW_ADDR = 0xff60e1,
  -- 显示方块的半长半高,单位为MAME的屏幕比例,考虑到实际是像素为单位,定义时用像素除以分辨率的形式
  --BOX_HALF_WIDTH = 4.0/224,
  --BOX_HALF_HEIGHT = 7.0/320,
  -- 中心方块的边框颜色和中心颜色
  BOX_BORDER_COLOR = 0x8800ff00,
  BOX_COLOR = 0x5500ff00,
  --大范围十字线辅助
  -- 中心十字线与边缘参考线的颜色
  --CENTER_LINE_COLOR = 0x88ff0000,
  SIDE_LINE_COLOR = 0xDDffd700,
  SIDE_LINE_FULL_COLOR = 0xFFff0000, -- 满能量时的颜色
  -- 中途需要变更的测试参数,准备了10个
  TEST_PARAMS = {5, 1.0/320.0, 1.0/224.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
  -- mame api用的对象,在refreshDevices()函数刷新获取
  mem = nil,
  container = nil,
  ui_container = nil,
  -- 当前是1p还是2p
  player = 1,
  -- 特定帧任务,只记录按下,按键时间至少相隔5帧以保证上一个键有时间放开
  -- 初始选关任务指令,port接口太多只好记录进去了...
  frame_missions_1p = { 
    --1p以瑞典机开始,前3关美国,英国,日本
    [300] = {fieldName = "Coin 1", port = ":SYSTEM", value = 1},
    [340] = {fieldName = "1 Player Start", port = ":SYSTEM", value = 1},
    [380] = {fieldName = "P1 Right", port = ":P1", value = 1},
    [420] = {fieldName = "P1 Right", port = ":P1", value = 1},
    [460+25] = {fieldName = "P1 Button 2", port = ":P1", value = 1}
  },
  frame_missions_2p = {  --2p以美国机开始,前3关瑞典,
    [300] = {fieldName = "Coin 2", port = ":SYSTEM", value = 1},
    [340] = {fieldName = "2 Players Start", port = ":SYSTEM", value = 1},
    [380+5] = {fieldName = "P2 Button 2", port = ":P2", value = 1}
    --[380+5] = {mask = 16, port = ":P2", value = 1}
  }
}

-- 完成任务指令,每完成一个按键指令添加一个3帧后的放开指令
function GAME.doMissions()
  local portName, field, frame_number, mission, frame_missions
  frame_number = manager.machine.screens[":screen"]:frame_number()
  -- 最大帧数保护,超过直接返回提高运行效率
  if frame_number > 1000 then
    return
  end
  -- 根据player设定选择任务
  if GAME.player == 2 then
    frame_missions = GAME.frame_missions_2p
  else
    frame_missions = GAME.frame_missions_1p
  end
  -- 执行任务
  mission = frame_missions[frame_number]
  if mission then
    portName = mission.port
    -- 对于按下键的情况,添加放开键的操作
    if mission.value > 0 then
      if mission.fieldName then
        frame_missions[frame_number+3] = {fieldName = mission.fieldName, port = portName, value = 0}
      else
        frame_missions[frame_number+3] = {mask = mission.mask, port = portName, value = 0}
      end
    end
    -- 执行操作
    if mission.fieldName then
      field = manager.machine.ioport.ports[portName].fields[mission.fieldName]
    else
      field = manager.machine.ioport.ports[portName]:field(mission.mask)
    end
    field:set_value(mission.value)
    print("mission "..frame_number.." finished!")
  end
end

-- 刷新设备,成功获取设备返回true,否返回false
function GAME.refreshDevices()
  GAME.mem = manager.machine.devices[':maincpu'].spaces['program']
  GAME.container = manager.machine.screens[":screen"].container
  GAME.ui_container = manager.machine.render.ui_container
  
  if (GAME.mem and GAME.container and GAME.ui_container) then
    return true
  else
    return false
  end
end

-- 坐标变换函数,从得到的坐标变为屏幕显示位置(是个比例),返回0.0-1.0的浮点数,
-- 由于是两侧坐标,返回两个值
function GAME.getX()
  local xleft, xright
  xleft = GAME.mem:read_u8(GAME.XLEFT_ADDR_LOW)
  xright = GAME.mem:read_u8(GAME.XRIGHT_ADDR_LOW)
  xright = (xright-1)+(xright-xleft) -- 对称法估计右边界
  return xleft/GAME.X_RESOLUTION, xright/GAME.X_RESOLUTION
end
function GAME.getY()
  local ydown, yup
  --ydown = 1.0*(GAME.mem:read_u8(GAME.YDOWN_ADDR_LOW)+GAME.mem:read_u8(GAME.YUP_ADDR_HIGH)*256)/GAME.Y_RESOLUTION
  yup = 1.0*(GAME.mem:read_u8(GAME.YUP_ADDR_LOW)+GAME.mem:read_u8(GAME.YUP_ADDR_HIGH)*256)
  ydown = yup-10
  yup = yup+GAME.TEST_PARAMS[1] -- 预计略高一点
  return ydown/GAME.Y_RESOLUTION, yup/GAME.Y_RESOLUTION
end

-- 数据显示
function GAME.showInfo(GAMEList, position, color)
  local index, value
  for index, value in ipairs(GAMEList) do
    GAME.ui_container:draw_text(position, 0.05*index, value, color)
  end
  
end

-- 显示监视的数据
function GAME.check()
  local power
  power = GAME.mem:read_u8(GAME.POW_ADDR)
  GAME.container:draw_text(0, 0, "P="..power, 0xFF005500, 0xFFcccccc)
  --GAME.ui_container:draw_text(0.05, 0.05, manager.machine.screens[":screen"]:frame_number()) 
  --[[
  GAME.showInfo({
    manager.machine.screens[":screen"]:frame_number()
    --GAME.mem:read_u8(GAME.XLEFT_ADDR_LOW),
    --GAME.mem:read_u8(GAME.XRIGHT_ADDR_LOW),
    --GAME.mem:read_u8(GAME.YDOWN_ADDR_LOW),
    --GAME.mem:read_u8(GAME.YUP_ADDR_LOW),
    --GAME.mem:read_u8(GAME.POW_LOW),
  } ,0.1, 0xFFffffff)
  ]]
end

-- 显示十字线
function GAME.showCross(xleft, xright, ydown, yup)
  local color
  if (GAME.mem:read_u8(GAME.POW_ADDR) < 3) then
    color = GAME.SIDE_LINE_COLOR
  else
    color = GAME.SIDE_LINE_FULL_COLOR
  end
  
  --[[一般直线
  GAME.container:draw_line(0.0, xleft, 1.0, xleft, color)
  GAME.container:draw_line(0.0, xright, 1.0, xright, color)
  GAME.container:draw_line(yup, 0.0, yup, 1.0, color)
  GAME.container:draw_line(ydown, 0.0, ydown, 1.0, color)
  ]]
  
  --粗线条
  GAME.container:draw_box(0.0, xleft-GAME.TEST_PARAMS[2], 1.0, xleft, color, color)
  GAME.container:draw_box(0.0, xright+GAME.TEST_PARAMS[2], 1.0, xright, color, color)
  GAME.container:draw_box(yup+GAME.TEST_PARAMS[3], 0.0, yup, 1.0, color, color)
  GAME.container:draw_box(ydown-GAME.TEST_PARAMS[3], 0.0, ydown, 1.0, color, color)
  
end

-- 显示中心方块,x,y是浮点数坐标
function GAME.showSqua(xleft, xright, ydown, yup)

  GAME.container:draw_box(
    ydown, xleft, 
    yup, xright, 
    GAME.BOX_BORDER_COLOR, GAME.BOX_COLOR
  )
  
end

-- 显示辅助线
function GAME.showAssist()
  GAME.ui_container:draw_box(0.49, 1.0, 0.51, 0.97, 0xDDdd00dd, 0xDDdd00dd) -- 中心
  GAME.ui_container:draw_line(0.0, 0.667, 1.0, 0.667, 0x88dddddd) -- 1/3道具回折线
end

-- 主函数,绑定后每一帧执行一次该函数
function GAME.main()
  local xleft, xright, ydown, yup
  
  -- 检查是否能执行
  if (not GAME.refreshDevices()) then
    return
  end
  
  xleft, xright = GAME.getX()
  ydown, yup = GAME.getY()
  
  -- 显示当前坐标
  --GAME.showInfo({xlow}, 0.05, 0xFF00ff00)
  --GAME.showInfo({yhigh, ylow}, 0.15, 0xFF0000ff)
  -- 显示数据监视
  GAME.check()
  -- 显示辅助线
  GAME.showAssist()
  -- 完成任务
  GAME.doMissions()
  if ((xleft < 0.001) or (xright < 0.001) or (yup < 0.001) or (ydown < 0.001)) then
    return
  end
  -- 显示十字线
  GAME.showCross(xleft, xright, ydown, yup)
  -- 显示角色中心方块
  GAME.showSqua(xleft, xright, ydown, yup)
  
end

-- 转换1P,2P,参数为2表示2p其他都是1p
function GAME.setPlayer(i)
  
  -- 1p数据
  GAME.player = 1
  GAME.XLEFT_ADDR_LOW = 0xff0711
  GAME.XRIGHT_ADDR_LOW = 0xff06cf
  GAME.YDOWN_ADDR_LOW = 0xff0713
  GAME.YUP_ADDR_LOW = 0xff06d1
  GAME.YUP_ADDR_HIGH = 0xff06d1-1
  GAME.POW_ADDR = 0xff60e1
  -- 2p数据
  if (i == 2) then
    GAME.player = 2
    GAME.XLEFT_ADDR_LOW = GAME.XLEFT_ADDR_LOW+0x60
    GAME.XRIGHT_ADDR_LOW = GAME.XRIGHT_ADDR_LOW+0x60
    GAME.YDOWN_ADDR_LOW = GAME.YDOWN_ADDR_LOW+0x60
    GAME.YUP_ADDR_LOW = GAME.YUP_ADDR_LOW+0x60
    GAME.YUP_ADDR_HIGH = GAME.YUP_ADDR_HIGH+0x60
    GAME.POW_ADDR = GAME.POW_ADDR+0x100
  end
  
end

-- 切换1p2p
function GAME.togglePlayer()
  if GAME.player == 2 then
    GAME.setPlayer(1)
  else
    GAME.setPlayer(2)
  end
  print("Player toggled!")
end

-- [[运行]]

-- 刷新api接口
GAME.refreshDevices()
-- 选1p或2p
GAME.setPlayer(2)
-- 导出到全局变量
_G.game = GAME
-- 绑定每一帧执行的函数,测试时绑定到debug
--myMame.debugRunPerFrame(GAME.main, 'aerofgt')
myMame.runPerFrame(GAME.main, 'aerofgt')
myMame.setHotkey("KEYCODE_X", GAME.togglePlayer)

return GAME
